/*
 * Copyright (c) 2009 Nathaniel Houghton <nathan@brainwerk.org>
 *                    Robert Kirchgessner <rkirchge@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose with or without fee is hereby granted, provided that
 * the above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 */
 
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
 
#include <avr/eeprom.h>
#include <util/crc16.h>
#include <util/delay.h>

#include "elm.h"
#include "util.h"
#include "usart.h"
#include "config.h"
#include "event.h"
#include "nvm.h"
#include "cmd.h"
#include "obd_pid.h"
#include "elm_dtc.h"
#include "ui.h"

#define DTC_INIT_TRIES 2

void
num_dtc_cmd(struct cmd * cmd)
{
	char buf[32];
	char dtc_buf[6];
	uint8_t i;
	uint8_t count;
	
	elm_init_retry( &myelm, PROT_ISO9141 ); /*Init with retries*/

	if (myelm.initd == 0) {
		usart_puts(0, "DTC Check Failed. ELM could "
		    "not be initialized.\r\n");
		return;
	}
	
	usart_puts(0,"Getting DTC codes...\r\n");
	elm_dtc_update();
	
	if( elm_dtc_mil() )
		usart_puts(0,"Your check engine light is on.\r\n");
			
	count = elm_dtc_count();
	
	if( count == 0 ) {
		usart_puts(0, "No trouble codes detected.\r\n");
		return;
	}
	
	snprintf(buf,sizeof(buf),"Detected %d trouble codes\r\n",
	    count );
	usart_puts(0,buf);
	
	for(i=0;i<count;++i) {
		DTC_CODE( dtc_buf, elm_dtc_current() );
		snprintf(buf,sizeof(buf),"%d: %s\r\n", i, dtc_buf);
		usart_puts(0, buf);
		elm_dtc_next();
	}
	
	return;
}

void
set_livemode_cmd(struct cmd *cmd)
{
	live_mode = 1;
	usart_puts(0, "OK\r\n");
}
 
void
read_cfg_cmd(struct cmd *cmd)
{
	uint8_t buf[128];

	eeprom_read_block(buf, 0, sizeof(cfg));

	usart_put(0, buf, sizeof(cfg));
}

void
write_cfg_cmd(struct cmd *cmd)
{
	struct config new_cfg;
	int i;
	uint8_t *p;

	p = (uint8_t *) &new_cfg;
	for (i = 0; i < sizeof(new_cfg); ++i) {
		*p = usart_getc(0);
		++p;
	}

	if (!valid_config(&new_cfg)) {
		usart_puts(0, "ERROR\r\n");
		return;
	} else
		usart_puts(0, "OK\r\n");

	/* update configuration */
	memcpy(&cfg, &new_cfg, sizeof(cfg));
	write_config(&cfg);
	
	/* re-init events */
	event_init(&cfg.event[0]);

	lcd_update(&lcd, 0, "Erasing flash...");
	lcd_update(&lcd, 1, "");

	/* erase flash */
	nvm_erase();
	
	usart_puts(0, "DONE\r\n");

	lcd_update(&lcd, 1, "  Done.");

	_delay_ms(300);

	menu_repaint();
}

void
read_cmd(struct cmd *cmd)
{
	uint32_t beg, end, len;
	uint8_t buf[128];

	if (cmd->argc != 3) {
		snprintf((char *) buf, sizeof(buf),
		    "usage: %s start_addr length\r\n", cmd->argv[0]);
		usart_puts(0, (char *) buf);
		return;
	}

	beg = hexstr(cmd->argv[1], strlen(cmd->argv[1]));
	len = atol(cmd->argv[2]);
	
	if (len == 0) {
		usart_puts(0, "invalid length\r\n");
		return;
	}

	end = len + beg - 1;

	while (beg <= end) {
		len = end - beg + 1;
		if (len > sizeof(buf))
			len = sizeof(buf);

		if (!strcasecmp(cmd->argv[0], "reade"))
			eeprom_read_block(buf, beg, len);
		else if (!strcasecmp(cmd->argv[0], "readn"))
			nvm_read(buf, beg, len);

		hexdump(buf, len);

		beg += len;
	}

	usart_puts(0, "done.\r\n");
}

void
read_log_cmd(struct cmd *cmd)
{
	uint32_t beg, len;
	uint8_t buf[128];
	int i;
	uint16_t crc;

	if (cmd->argc != 3)
		return;
	
	beg = hexstr(cmd->argv[1], strlen(cmd->argv[1]));
	len = atol(cmd->argv[2]);
	if (len < 1 || len > 126) {
		usart_puts(0, "invalid length\r\n");
		return;
	}

	nvm_read(buf, beg, len);

	buf[len] = 0;
	buf[len + 1] = 0;
	crc = 0xffff;
	for (i = 0; i < len + 2; ++i)
		crc = _crc16_update(crc, buf[i]);

	memcpy(&buf[len], &crc, sizeof(crc));

	usart_put(0, buf, len + 2);
}

void
erase_nvm_cmd(void)
{
	usart_puts(0, "Erasing flash...");
	lcd_update(&lcd, 0, "Erasing flash...");
	lcd_update(&lcd, 1, "");
	nvm_erase();
	usart_puts(0, "\r\nErased.\r\n");
	lcd_update(&lcd, 1, "  Done.");
	cfg.nvm_start_addr = 0;
	write_config(&cfg);
	menu_repaint();
	_delay_ms(300);
}

void
echo_cmd(struct cmd *cmd)
{
	uint8_t echo;
	char buf[64];

	echo = -1;
	if (cmd->argc == 2) {
		echo = atoi(cmd->argv[1]);
	}

	if (echo < 0 || echo > 1) {
		snprintf(buf, sizeof(buf), "usage: echo <0 or 1>\r\n");
		usart_puts(0, buf);
	} else {
		usart_puts(0, "OK\r\n");
		usart_echo(0, echo);
	}
}

void
parse_line(struct cmd *v)
{
	int c;
	int i;
	static char parse_buf[WC][WORD_LEN];

	v->argc = -1;

	i = usart_peek(0, '\r');
	if (i == 0)
		return;
	else if (i == -1) {
		usart_flush(0);
		return;
	}

	v->argc = 0;

	while ((c = usart_getc(0)) != '\r') {
		if (isspace(c))
			continue;

		i = 0;
		v->argv[v->argc] = parse_buf[v->argc];
		do {
			v->argv[v->argc][i] = c;
			c = usart_getc(0);
			i++;
		} while (!isspace(c) && i < WORD_LEN - 1);
		v->argv[v->argc][i] = '\0';

		/* clean up rest of overflowing word */
		while (!isspace(c))
			c = usart_getc(0);

		++v->argc;

		if (v->argc == WC) {
			/* clean up rest of line */
			while (c != '\r')
				c = usart_getc(0);
			break;
		}

		if (c == '\r')
			break;
	}
}
